fix(rest): the untyped /meta/diagnostics sweep forwards the caller's organization, so a governance summary stops undercounting its own drill-down (#15622) - #15726
Merged
hotlong merged 2 commits intoSep 5, 2026
Conversation
…organization, so a governance summary stops undercounting its own drill-down (#15622) `GET /api/v1/meta/diagnostics` has two arms. The `?type=` arm has stated the organization since #13753; the untyped whole-registry sweep passed nothing, so the Studio governance summary reported clean tiles over a partition it never read while the per-type screen you reach by clicking into it could see the same items. A problem-reporting surface that structurally cannot see a class of problems issues a false all-clear. The obstacle recorded in the tree was that one `organizationId` could not express a per-type scope from this door without a fan-out per overridable type and a REST-side re-aggregation of `total`/`stats`/`scannedTypes`. #14683 dissolved it and #15034 recorded that: `getMetaDiagnostics` loops `for (const t of targetTypes)` calling `getMetaItems({ type: t, organizationId, ... })`, and the first thing `getMetaItems` does with that organization is `organizationIdForMetaRead(request.type, ...)` on its OWN folded type. One org id is therefore already narrowed per type by the callee. The door now resolves the memoised exec ctx for BOTH arms and passes `ctx?.tenantId` RAW on the untyped one. Deliberately NOT pre-folded with `organizationIdForMetaRead(...)` the way the `?type=` arm folds: there is no single type to fold on, and folding on any one of them would suppress the organization for every type at once. Identical in shape to the `/references` repair (e13ede8, #13753). The pin `an org-scoped item is absent from the whole-registry sweep` carried an explicit "if this reddens, read the card before making it green" note; #15622 is that card, so the assertion is REPLACED by its inverse rather than deleted, with the comment still pointing at the card. Beside it stands the narrowness control #15622 named as missing: in ONE request a planted pre-#6190 org-scoped row on a non-overridable type does NOT appear while an overridable type's org-authored row DOES — the half that tells a per-type gate from an unconditional tenant. Controls pin that org B is not served org A's items, that an organization-less caller reads exactly what it read before, that an env-wide item stays visible to an org caller, and that the wire shape is unchanged (same five response keys, same `stats` row keys, 200 either way). `resolveExecCtx` census numbers are unmoved: still 77 sites / 98 mentions, the hoisted resolution being the same single locally-caught continuation-line site. No new parameter, response field, status code or contract surface: ADR-0131 D6/D7 retires this partition in v18 (#15206, C5), so nothing is built on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s comment shifted `check:check-system-context-census --fix` output: the five `rest-server.ts` elevation-read anchors on the system-context page moved by the same +32 lines the untyped arm's decision record added. Pure line rot, no row content changed. Part of #15622 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
hotlong
deleted the
claude/issue-15622-diagnostics-sweep-org-forwarding
branch
September 5, 2026 05:43
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15622
GET /api/v1/meta/diagnosticshas two arms. The?type=arm has stated the caller's organization since #13753; the untyped whole-registry sweep passed nothing. So the Studio governance summary reported clean tiles over a partition it never read, and undercounted relative to the per-type drill-down screen you reach by clicking into it. A summary whose whole job is surfacing problems, and which structurally cannot see a class of them while its own drill-down can, issues a false all-clear.Ruled on the card (comment 5549255080): the untyped arm forwards the caller's organization, raw.
The four premises, re-verified on
origin/mainby this seat before anything was writtenAll four hold, measured at merge base
1c1421401:getMetaDiagnostics(packages/metadata-protocol/src/protocol.ts:6293) loopsfor (const t of targetTypes)and callsgetMetaItems({ type: t, organizationId: request.organizationId, packageId: request.packageId }). The organization is spent per type, not once.getMetaItemscomputesorganizationIdForMetaRead(request.type, request.organizationId)atprotocol.ts:6958, on its ownrequest.type, one statement afterrequest = canonicalizeMetaRequestType(request)— i.e. after the canonical fold.organizationIdForMetaRead(type, org)(packages/metadata-core/src/meta-write-org-scope.ts:184) returnsundefinedwhenactiveOrganizationIdis undefined, and otherwisedeclaresOrgOverride(type) ? activeOrganizationId : undefined. It returns the org only for an overridable type.diagnosticsOrganizationIdonly insideif (diagnosticsType)— the untyped sweep passed nothing at all.⇒ One
organizationIdhanded to the untyped arm is already narrowed per type by the callee. The obstacle written in the tree ("one org id cannot express a per-type scope") is gone.The change
packages/rest/src/rest-server.ts— the untyped arm resolves the memoised exec ctx and passesorganizationId: ctx?.tenantIdRAW.⛔ Deliberately not pre-folded with
organizationIdForMetaRead(...)the way the?type=arm folds: there is no single type to fold on for a whole-registry sweep, and folding on any one of them would suppress the organization for every type at once. The per-type decision belongs to the callee's loop. Identical in shape to the/referencesrepair that landed ase13ede817(#13753 / PR #15689), including its narrowness control, which was read first and used as the model.The
resolveExecCtxresolution is hoisted, not duplicated: still 77 sites / 98 mentions, still the same single locally-caught continuation-line spellingexecctx-consumer-censusreads. That census file is unmodified and green.⛔ Untouched, as scoped: the
?type=arm;protocol.ts; any new parameter, spec key, response field, status code or contract surface. ADR-0131 D6/D7 retires this partition in v18 (#15206, C5), so nothing new is built on it — the door's comment says so in place.The pin was REPLACED, not deleted
an org-scoped item is absent from the whole-registry sweepcarried an explicit "if this reddens, read the card before making it green" note. #15622 is that card, so its assertion is inverted in place, with the comment still naming the card and explaining the flip.Beside it stands the phantom control the card named as missing — there was none on the untyped arm (the pre-existing one covers only
?type=object). In one request: a planted pre-#6190 org-scoped row on the non-overridableobjecttype does not appear, while the overridableview's org-authored row does. Neither assertion states that fact alone.Controls also pin that org B is not served org A's items, that an organization-less caller reads exactly what it read before, that an env-wide item stays visible to an org caller, and that the wire shape is unchanged (same five response keys, same
statsrow keys —count/locked/packages— and 200 either way).Ablations — both legs proven on disk, never from an editor's exit code
M1 — can the new pins go red? Restored only
rest-server.tsto base and left everything else in place.git hash-objectmoved1f1ee559ad595fa6524eddf78310aa2b392afc3b(HEAD blob) →2ad2be6257cb005e0d48b58ed761c61fc5396019(base blob), and the marker string[#15622] The whole-registry armwent from 1 occurrence to 0.⭐ THE CARD: an org-authored item on an overridable type IS counted(expected +0 to be 1) and the narrowness control's org-visibility half (the overridable type lost its org scope on the same request — the gate is not per type). The phantom-exclusion half stayed green, correctly: the base door names no organization at all.1f1ee559ad..., marker back to 1,git diff HEADfor that path empty. The restore usedgit checkout HEAD -- "$ABS_PATH"with an absolute path resolved fromgit rev-parse --show-toplevel, under atrap ... EXIT INT TERM.M2 — does the narrowness control have teeth against an unconditional tenant? M1 cannot answer that, because the door cannot bypass the callee. So the callee's gate was ablated instead:
protocol.ts:6958organizationIdForMetaRead(request.type, request.organizationId)replaced by the rawrequest.organizationId.packages/rest's vitest config does not alias@objectstack/metadata-protocol, so it resolves throughdist/. Every leg rebuilt the package and confirmed the artifact before any result was read./* … */comment, which tsup strips, andscripts/ablation-dist-preflight.mjsrefused it: "marker found ONLY in 2 sourcemap files and in no executable output … Treat this run as void." It was re-run with a marker that survives into emitted code.pnpm --filter @objectstack/metadata-protocol buildthe preflight reported "marker present in 2 built files" (dist/index.js,dist/index.cjs).expected 2 to be 1: the phantom was resurrected intostats.object.count) and the sibling/referencesnarrowness control. ⭐ The pre-existing?type=objectphantom control did not redden, because the typed arm pre-folds at the door and the callee never receives an org. That is precisely why the new untyped control is the one with teeth on the callee's gate.git diff HEADempty, rebuilt, andablation-dist-preflight.mjs --absentreported "marker absent from all 24 built files" plus "working tree clean against HEAD".Whole-tree
git status --porcelainwas empty after both ablations, and the restored pin file re-ran 41 passed (41).Verification
Union re-run at final commit
7ce299574, after the last commit, not before it.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— let it derive its own change set (3 paths, then 4 once the docs re-anchor landed). 84 families, 84 green, 0 red, 0 NOT MEASURED. Exit codes captured by redirecting first (cmd > file 2>&1; EXIT=$?), never read after a pipe.PREREQUISITE NOT MET(exit 3 — NOT MEASURED, not red):check:dual-build-cjs-loadsandcheck:type-check-debtboth need a built workspace. The closure was built (turbo run build --concurrency=2 --filter='./packages/*' --filter='./packages/*/*', 71/71 successful) and both then measured green: dual-build103/66/619/1against floors90/58/520/1; type-check debt12 ledger entries re-measured, 140 raw errors, none above its recorded number.check:check-system-context-censusreported real line rot (the door's decision record adds +32 lines). Repaired with its own--fix, which rewrote fiverest-server.tsanchors on the system-context page; row content unchanged. Re-run:OK — 106 elevation read sites … 140 anchors resolve.pnpm --filter @objectstack/rest test— 180 files / 3086 tests passed.pnpm --filter @objectstack/rest typecheck— exit 0.**/*.test.ts, andtsc --noEmit --listFilesconfirms it —rest-server.tsappears 1 time in the program,rest-server-meta-read-org-scope.test.ts0 times. The test layer is covered instead bypnpm --filter @objectstack/rest run check:test-typecheck, run separately: OK, 0 files / 0 errors held in debt.@objectstack/metadata-protocol(163 files / 2387 tests),@objectstack/spec(468 files / 12672 tests),@objectstack/client(33 files / 437 tests) — all green.pnpm lint— the repo-wideeslint . --no-inline-config, run in full rather than narrowed. Exit 0 in 25s, so no narrowing needs declaring.grep -naPcontrol-character self-scan over both edited source files: no matches.scripts/pm/os-verify-lock.sh, and every one of its VERDICT lines saidUNLOCKED (declared) · no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized. Wall-clock figures above are therefore shared-box readings, not quiet-machine promises.Generated by Claude Code